﻿using DDDB;
using System.Diagnostics;
using System.Runtime.Intrinsics.Arm;
using System.Security.Cryptography;

namespace demo
{
    internal class Program
    {
        static string testPath = "c:\\ddd_test";
        static Dictionary<string, TestItem> testfunc = new Dictionary<string, TestItem>();
        static void Main(string[] args)
        {

            InitMenu();

            while (true)
            {
                ShowMenu();
                var line = Console.ReadLine();

                if (testfunc.ContainsKey(line))
                {
                    Console.WriteLine("--------test begin--");
                    testfunc[line].func();
                    Console.WriteLine("--------test end----");
                }
            }
        }


        class TestItem
        {
            public string desc;
            public Action func;
            public TestItem(string desc, Action func)
            {
                this.desc = desc;
                this.func = func;
            }
        }
        static void InitMenu()
        {
            testfunc["1"] = new TestItem("Test hash performence", TestHashPerform);
            testfunc["2"] = new TestItem("Test hash same rate 24bit+28bit", TestHashSame);
            //分多组和提高bit相比毫无意义
            //testfunc["3"] = new TestItem("Test hash same rate 24bit+24bit+24bit", TestHashSame24_24_24);
            testfunc["3"] = new TestItem("Test FilterSync", TestFilterSync);
            testfunc["4"] = new TestItem("Test TestDataFile", TestDataFile);
            testfunc["5"] = new TestItem("Test DB write 1000000", TestWrite10W);
            testfunc["6"] = new TestItem("Test DB write samekey", TestDBSameKey);
            testfunc["7"] = new TestItem("Test DB write MultiThread", TestWrite_MultiThread);

        }
        static void ShowMenu()
        {
            Console.WriteLine();

            Console.WriteLine("==dddb testor==");

            Console.WriteLine("testpath=" + testPath);
            foreach (var key in testfunc)
            {
                Console.WriteLine("[" + key.Key + "] " + key.Value.desc);
            }
            Console.WriteLine("type number and enter to run a test.");
        }

        //Hash算法性能分析
        static void TestHashPerform()
        {
            Console.WriteLine("==test hash performance");
            Console.WriteLine("sha256是久经考验的hash算法，我们将对key做sha256进行保存，所有这个算法的性能很重要，这里我们测试多种实现，找出最快的那个");
            Console.WriteLine("除了GPU以外，性能最高的就是dotnet 内置的实现，但是gpu只适合大并发计算，这个场景不适合。");
            {
                var syshashb = SHA256.Create();
                long crccount = 0;
                DateTime t0 = DateTime.Now;
                for (var i = 0; i < 10000000; i++)
                {
                    var guid = Guid.NewGuid().ToByteArray();
                    var hash = syshashb.ComputeHash(guid);
                    crccount += hash[1];
                }
                DateTime t1 = DateTime.Now;
                Console.WriteLine("sys sha256 time=" + (t1 - t0).TotalSeconds);
            }
            {
                var syshashb = new Org.BouncyCastle.Crypto.Digests.Sha256Digest();

                long crccount = 0;
                DateTime t0 = DateTime.Now;
                for (var i = 0; i < 10000000; i++)
                {
                    var guid = Guid.NewGuid().ToByteArray();
                    syshashb.Reset();
                    syshashb.BlockUpdate(guid, 0, guid.Length);
                    byte[] hash = new byte[32];
                    syshashb.DoFinal(hash, 0);

                    crccount += hash[1];
                }
                DateTime t1 = DateTime.Now;
                Console.WriteLine("BouncyCastle sha256 time=" + (t1 - t0).TotalSeconds);
            }
            {

                var syshashb = new Org.BouncyCastle.Crypto.Digests.Sha3Digest(256);

                long crccount = 0;
                DateTime t0 = DateTime.Now;
                for (var i = 0; i < 10000000; i++)
                {
                    var guid = Guid.NewGuid().ToByteArray();
                    syshashb.Reset();
                    syshashb.BlockUpdate(guid, 0, guid.Length);
                    byte[] hash = new byte[32];
                    syshashb.DoFinal(hash, 0);

                    crccount += hash[1];
                }
                DateTime t1 = DateTime.Now;
                Console.WriteLine("BouncyCastle sha3-256 time=" + (t1 - t0).TotalSeconds);
            }
            //{

            //    var syshashb = HashLib.HashFactory.Crypto.SHA3.CreateKeccak256();

            //    long crccount = 0;
            //    DateTime t0 = DateTime.Now;
            //    for (var i = 0; i < 10000000; i++)
            //    {
            //        var guid = Guid.NewGuid().ToByteArray();
            //        var hash = syshashb.ComputeBytes(guid);


            //        crccount += hash.GetBytes()[1];
            //    }
            //    DateTime t1 = DateTime.Now;
            //    Console.WriteLine("HashLib sha3-256 time=" + (t1 - t0).TotalSeconds);
            //}

        }

        //Hash算法碰撞率分析
        static unsafe void TestHashSame()
        {
            Console.WriteLine("==test hash same rate");

            Console.WriteLine("sha256的碰撞率可以直接认为是0,但是为了高性能的检索，我们实际能使用的key 没有那么大");
            Console.WriteLine("我们打算采取(24bit~30bit)的数据进行散列，将数据分成若干组存储，然后附加一个(28~32)bit的数据，一起用来快速排除不存在的数据");
            Console.WriteLine("千万级别数据，24+28数据碰撞率不足百分之一");
            Console.WriteLine("五千万级数据，26+30bit数据碰撞率不足百分之一");
            Console.WriteLine("亿级数据，28+32bit数据碰撞率不足百分之一");

            Console.WriteLine("推荐组合 28+32 ，key1G容量 bf512MB容量，但是数据冲突率很小，99.7%的数据无冲突，一次可判断是否存在");
            int forebit = 26;
            int afterbit = 30;

            DDDB.BoomFilter bfhigh = new DDDB.BoomFilter(afterbit);
            PosFilter flow = new PosFilter(forebit);
            Console.WriteLine("测试(key)" + forebit + "bit + (boomfilter)" + afterbit + "bit");
            Console.WriteLine("bf内存：" + (bfhigh.MemorySize / 1024 / 1024) + "MB");
            Console.WriteLine("key内存：" + (flow.MemorySize / 1024 / 1024) + "MB");
            List<uint> SameHigh = new List<uint>();
            List<uint> SameLow = new List<uint>();
            //List<int> SameMid = new List<int>();
            List<uint> SameBoth = new List<uint>();
            int testcount = 50000000;
            {
                var syshashb = SHA256.Create();
                DateTime t0 = DateTime.Now;
                for (var i = 0; i < testcount; i++)
                {
                    var guid = Guid.NewGuid().ToByteArray();
                    var hash = syshashb.ComputeHash(guid);
                    fixed (byte* phash = hash)
                    {
                        uint low3 = flow.GetIndexWithMask(((uint*)phash)[0]);
                        //& 0x00ffffff;
                        uint high3 = bfhigh.GetIndexWithMask(((uint*)phash)[7]);
                        //& bfhigh.BitMask;
                        var oldh = bfhigh.Get(high3);
                        var oldl = flow.Get(low3);
                        //var oldm = sametestm[m3];
                        if (oldh)
                        {
                            SameHigh.Add(high3);
                        }
                        if (oldl.IsEmpty == false)
                        {
                            SameLow.Add(low3);
                        }
                        //if (oldm == 1)
                        //{
                        //    SameMid.Add(m3);
                        //}

                        if (oldh && oldl.IsEmpty == false) // && oldm > 0)
                        {
                            SameBoth.Add((uint)i);
                        }
                        bfhigh.Set(high3);
                        oldl.datastart++;
                        flow.Set(low3, oldl);
                    }
                }
                DateTime t1 = DateTime.Now;
                Console.WriteLine("sys sha256 time=" + (t1 - t0).TotalSeconds);

            }



            Console.WriteLine("前" + forebit + "冲突" + SameLow.Count + "次");
            Console.WriteLine("后" + afterbit + "冲突" + SameHigh.Count + "次");
            Console.WriteLine("一起冲突" + SameBoth.Count + "次");
            var rate = (double)SameBoth.Count / (double)testcount * 100;
            Console.WriteLine("冲突率:" + Math.Round(rate, 6) + "%");
            int samecount = 0;
            int allcount = 0;
            int havecount = 0;
            for (var i = 0; i < flow.Count; i++)
            {
                var s = flow.Get((uint)i);
                if (samecount < s.datastart)
                {
                    samecount = s.datastart;
                }
                allcount += s.datastart;
                if (s.datastart > 0)
                    havecount++;
            }
            var acount = (float)allcount / flow.Count;
            var acount2 = (float)allcount / havecount;
            Console.WriteLine("最大冲突队列长度=" + samecount + "表示一个key下要存多少值" + " 平均" + acount + " 平均有效" + acount2);
        }

        static void TestFilterSync()
        {
            Console.WriteLine("test boomfilter");
            {
                string bffile = "bf.bin";
                var bf = new BoomFilter(24, bffile);
                bf.Set(4);
                bf.Set(8);
                bool b4 = bf.Get(4);
                bool b5 = bf.Get(5);

                bool b8 = bf.Get(8);
                Debug.Assert(b4, "b4 error");
                Debug.Assert(b5 == false, "b5 error");
                Debug.Assert(b8, "b8 error");
                bf.Dispose();

                var bf2 = new BoomFilter(24, bffile);
                b4 = bf2.Get(4);
                b5 = bf2.Get(5);
                b8 = bf2.Get(8);
                Debug.Assert(b4, "b4 error");
                Debug.Assert(b5 == false, "b5 error");
                Debug.Assert(b8, "b8 error");
                bf2.Dispose();

                System.IO.File.Delete("bf.bin");
                Console.WriteLine("test boomfilter ok");
            }
            Console.WriteLine("test posfilter");
            {
                string bffile = "index.bin";
                var bf = new PosFilter(24, bffile);
                bf.Set(4, new DataPos { datafile = 4, datastart = 4 * 3 });
                bf.Set(8, new DataPos { datafile = 5, datastart = 8 });
                var b4 = bf.Get(4);
                var b5 = bf.Get(5);
                var b8 = bf.Get(8);
                var bs = bf.GetFileRange();
                Debug.Assert(b4.datafile == 4 && b4.datastart == 12, "b4 error");
                Debug.Assert(b5.IsEmpty, "b5 error");
                Debug.Assert(b8.datafile == 5 && b8.datastart == 8, "b8 error");
                Debug.Assert(bs.start == 0 && bs.end == 0, "bs error");

                bf.SetFileRange(new FileRange { start = 0, end = 1 });
                bf.Dispose();

                var bf2 = new PosFilter(24, bffile);
                b4 = bf2.Get(4);
                b5 = bf2.Get(5);
                b8 = bf2.Get(8);
                bs = bf.GetFileRange();
                Debug.Assert(b4.datafile == 4 && b4.datastart == 12, "b4 error");
                Debug.Assert(b5.IsEmpty, "b5 error");
                Debug.Assert(b8.datafile == 5 && b8.datastart == 8, "b8 error");
                Debug.Assert(bs.start == 0 && bs.end == 1, "bs error");
                bf2.Dispose();

                System.IO.File.Delete("index.bin");
                Console.WriteLine("test posfilter ok");
            }
        }
        unsafe static void TestDataFile()
        {
            Console.WriteLine("df base test");
            Random ran = new Random();
            byte[] testkey = new byte[32];
            byte[] testkey2 = new byte[32];
            ran.NextBytes(testkey);
            {
                var df = new DataFile(".", 4);
                DataHead head = df.Uncheck_ReadHead(1);
                var headkey = df.Uncheck_ReadHeadWithKey(1);
                unsafe
                {
                    Console.WriteLine("sizeof DataHead=" + sizeof(DataHead));
                    Console.WriteLine("sizeof DataHeadWithKey=" + sizeof(DataHeadWithKey));
                }
                Console.WriteLine("head 1 tag=" + head.tag);
                Console.WriteLine("head 1 len=" + head.len);
                Console.WriteLine("head 1 useblock=" + head.useblock);
                Console.WriteLine("head 1 key=" + headkey.GetStringKey());

                if (head.IsEmpty)
                {
                    head.tag = 1;
                    head.useblock = 3;
                    df.Uncheck_WriteHead(1, head);
                    var head2 = new DataHeadWithKey();
                    head2.tag = 1;
                    head2.useblock = 3;
                    head2.SetKey(testkey);
                    df.Uncheck_WriteHeadWithKey(4, head2);

                    var head3 = new DataHeadWithKey();
                    head3.tag = 1;
                    head3.useblock = 3;
                    head3.key[5] = 8;
                    head3.len = 256;
                    byte[] data = new byte[256];

                    ran.NextBytes(data);
                    df.Uncheck_WriteHeadWithKeyAndData(7, head3, data);
                }

                df.Dispose();
            }
            {//遍历一个数据文件的方式
                var df = new DataFile(".", 4);

                DataPos pos = new DataPos { datafile = 4, datastart = 4 };
                var key = df.FindKey(testkey, ref pos, out DataHeadWithKey _, out DataPos _, out DataHeadWithKey _);
                Console.WriteLine("find key1=" + key + " pos=" + pos.datafile + "-" + pos.datastart);


                DataHeadWithKey head;
                int startpos = 1;
                do
                {
                    head = df.Uncheck_ReadHeadWithKey(startpos);
                    if (head.tag == 1)
                    {
                        var data = df.Uncheck_ReadData(startpos, (int)head.len);
                        Console.WriteLine("data len=" + data.Length);
                    }
                    if (head.tag != 0)
                    {
                        Console.WriteLine("head at:" + startpos + " tag==" + head.tag + " blocklen=" + head.useblock + " key=" + head.GetStringKey());
                        if (head.nextpos.IsEmpty)
                        {
                            Console.WriteLine("head next[end]");
                        }
                        else
                        {
                            Console.WriteLine("head next:" + head.nextpos.datafile + "-" + head.nextpos.datastart);
                        }

                        startpos += (int)head.useblock;
                    }


                }
                while (head.tag != 0);
                df.Dispose();



                System.IO.File.Delete(df.filename);
            }
        }

        static void TestWrite10W()
        {
            int keyc = 0;
            //Database.debug_onputsamekey = (key) =>
            //{
            //    keyc++;
            //    if (keyc < 5)
            //    {
            //        Console.WriteLine("write key sp=" + KeyTool.GetKeyString(key));
            //    }
            //};
            if (System.IO.Directory.Exists(testPath))
                System.IO.Directory.Delete(testPath, true);
            DateTime time0 = DateTime.Now; ;
            Database db = new Database(testPath);
            DateTime time1 = DateTime.Now;
            Console.WriteLine("db init time=" + (time1 - time0).TotalSeconds);
            Random random = new Random();
            List<byte[]> keys = new List<byte[]>();
            int loop = 100000;

            for (var i = 0; i < loop; i++)
            {
                var guid = Guid.NewGuid().ToByteArray();
                var key = KeyTool.GetHashKey(guid);
                keys.Add(key);
            }
            DateTime time2 = DateTime.Now;
            Console.WriteLine("key time=" + (time2 - time1).TotalSeconds);

            byte[] checkdata = null;
            int checkpos = random.Next() % loop;
            byte[] value = new byte[22 * 1024];
            for (var i = 0; i < loop; i++)
            {
                random.NextBytes(value);
                db.Put(keys[i], value);
                if (checkpos == i)
                    checkdata = value.Clone() as byte[];
            }
            DateTime time3 = DateTime.Now; ;
            Console.WriteLine("db write time=" + (time3 - time2).TotalSeconds);

            int excount = 0;
            for (var i = 0; i < loop; i++)
            {
                if (db.Exist(keys[i], out DataPos pos))
                {
                    byte[] data = db.GetByPos(pos);
                    if (i == checkpos)
                    {
                        Console.WriteLine("抽检");
                        for (var j = 0; j < data.Length; j++)
                        {
                            if (data[j] != checkdata[j])
                            {
                                Console.WriteLine("抽检失败");
                                break;
                            }
                        }
                    }
                    excount++;
                }
            }
            DateTime time4 = DateTime.Now; ;
            Console.WriteLine("db same=" + excount);



            Console.WriteLine("db read time=" + (time4 - time3).TotalSeconds);
            db.Dispose();
        }

        static void TestDBSameKey()
        {
            DateTime time0 = DateTime.Now; ;
            Database db = new Database(testPath);
            DateTime time1 = DateTime.Now;
            Console.WriteLine("db init time=" + (time1 - time0).TotalSeconds);


            Random random = new Random();
            List<byte[]> keys = new List<byte[]>();
            int loop = 10;

            for (var i = 0; i < loop; i++)
            {
                var guid = Guid.NewGuid().ToByteArray();
                var key = KeyTool.GetHashKey(guid);
                keys.Add(key);
            }
            DateTime time2 = DateTime.Now;
            Console.WriteLine("key time=" + (time2 - time1).TotalSeconds);

            //write first
            {
                byte[] value = new byte[10 * 1024];
                for (var i = 0; i < loop; i++)
                {
                    random.NextBytes(value);
                    db.Put(keys[i], value);
                }
            }
            //write second
            {
                byte[] value = new byte[22 * 1024];
                for (var i = 0; i < loop; i++)
                {
                    random.NextBytes(value);
                    db.Put(keys[i], value);
                }
            }
            //write 3rd
            {
                byte[] value = new byte[44 * 1024];
                for (var i = 0; i < loop; i++)
                {
                    random.NextBytes(value);
                    db.Put(keys[i], value);
                }
            }

            DateTime time3 = DateTime.Now; ;
            Console.WriteLine("db write time=" + (time3 - time2).TotalSeconds);

            int excount = 0;
            for (var i = 0; i < loop; i++)
            {
                if (db.Exist(keys[i], out DataPos pos))
                {
                    byte[] data = db.GetByPos(pos);
                    excount++;
                }
            }
            DateTime time4 = DateTime.Now; ;
            Console.WriteLine("db same=" + excount);

            Console.WriteLine("db read time=" + (time4 - time3).TotalSeconds);
            db.Dispose();
        }


        static void TestWrite_MultiThread()
        {
            if (System.IO.Directory.Exists(testPath))
                System.IO.Directory.Delete(testPath, true);
           
            var task = TestWrite_MultiThread_Async();
            task.Wait();
          
        }
        static async Task TestWrite_MultiThread_Async()
        {
            DataBase_Async db = new DataBase_Async();
            DateTime time0 = DateTime.Now; ;
            await db.Init(testPath); 
            DateTime time1 = DateTime.Now; ;
            Console.WriteLine("db init time=" + (time1 - time0).TotalSeconds);
          
            long threaddone = 0;
            for (var i = 0; i < 10; i++)
            {
                var thread = new System.Threading.Thread(async () =>
                {
                    Random random = new Random();
                    for (var j = 0; j < 10000; j++)
                    {
                        var guid = Guid.NewGuid().ToByteArray();
                        var key = KeyTool.GetHashKey(guid);
                        byte[] value = new byte[22 * 1024];
                        db.Put(key, value);
                    }
                    //Console.WriteLine("write done a thread.");
                   
                });
                thread.IsBackground = true;
                thread.Start();
            }
            while(true)
            {
                await Task.Delay(100);
                if (db.HaveQueue == false)
                    break;
            }
            DateTime time2 = DateTime.Now; ;
            Console.WriteLine("db put time=" + (time2 - time1).TotalSeconds);
            db.Close();
        }

    }
}